﻿using System;
using System.ComponentModel;
using Chambills.Present.Core;
using Present.Commands.Async.Actions;
using System.Windows.Input;
using Present.Commands.Decorators;
using Present.Commands.Extensions;

namespace Present.Commands.Async
{





    /// <summary>
    /// Wraps a <see cref="BackgroundWorker"/> in a command for use with WPF.
    /// The command will execute on a background thread, passing the result to the gui thread for processing.
    /// </summary>
    public class AsyncCommand : ICommand
    {
        private readonly IAsyncAction target;

        public AsyncCommand(IAsyncAction target)
        {
            this.target = target;
            CanExecuteChanged += (sender, e) => { };
            ExecutionComplete += (sender, e) => { };
            target.CanExecuteChanged += (sender, e) => RaiseCanExecuteChanged();
        }

        /// <summary>
        /// Execute the command on a background thread.
        /// Either execute success or error handler on the GUI thread.
        /// </summary>
        public virtual void Execute(object parameter)
        {
            ExecuteWithOptionToCancel(parameter);
        }

        /// <summary>
        /// Execute the command on a background thread.
        /// Either execute success or error handler on the GUI thread.
        /// </summary>
        public virtual IDisposable ExecuteWithOptionToCancel(object parameter)
        {
            var actionArgs = target.PrepareWork(parameter);
            return RunWorker(actionArgs);
        }

        protected IDisposable RunWorker(ActionExecution execution)
        {
            var worker = BackgroundWorkerFactory.Create();
            worker.WorkerSupportsCancellation = true;
            worker.DoWork += (sender, e) => e.Result = target.DoWork(execution);
            worker.RunWorkerCompleted += (sender, e) => HandleResult(e, execution);
            worker.RunWorkerCompleted += (sender, e) => worker.NullDispose();
            worker.RunWorkerAsync();
            return new DisposableAction(() => execution.CancelRequested = true);
        }


        protected virtual void HandleResult(RunWorkerCompletedEventArgs e, ActionExecution execution)
        {
            try
            {
                if (e.Error != null)
                    target.HandleError(e.Error);
                else if (execution.CancelRequested)
                    target.HandleCancel(execution);
                else
                    target.HandleSuccess(e.Result);
            }
            finally
            {
                OnExecutionComplete();
                RaiseCanExecuteChanged();
            }
        }

        private void OnExecutionComplete()
        {
            ExecutionComplete(this, EventArgs.Empty);
        }

        private void RaiseCanExecuteChanged()
        {
            CanExecuteChanged(this, EventArgs.Empty);
        }


        public bool CanExecute(object parameter)
        {
            return target.CanExecute(parameter);
        }

        public event EventHandler CanExecuteChanged;
        public event EventHandler ExecutionComplete;
    }
}